#!/bin/sh

#this script is brutal and verbose, has no tricks and is quite linear, then
#quite easy to deal with
#for the moment, it's hardcoded for a gcc toolchain... BAD! Since now
#gcc is a c++ piece of shit

#stolen from ffmpeg configure like a pig
set -e

#prevent locale nonsense from breaking basic text processing.
LC_ALL=C
export LC_ALL

init_file_name=init

init_ulinux_src_files='
ulinux/utils/ascii/string/vsprintf.c
ulinux/utils/mem.c
ulinux/utils/ascii/string/conv/decimal/decimal.c
'

init_src_files="
uevents.c
modules.c
uevent.c
init.c
ramfs.c
$init_ulinux_src_files
"

clean_do()
{
    rm -f static_modules.h
    rm -f *.cpio.xz
    rm -f *.cpio
    rm -f cpio
    rm -f $init_file_name
    for init_src_file in $init_src_files
    do
      init_pp_file=${init_src_file%.c}
      init_pp_file=${init_pp_file}.pp.c
      rm -f ${init_pp_file}
      init_o_file=${init_src_file%.c}
      init_o_file=${init_o_file}.o
      rm -f ${init_o_file}
      #clean directories, but keep root of build tree
      tgt_dir=$(dirname $init_src_file)
      if test -d $tgt_dir -a "$tgt_dir" != "."; then
        rmdir --ignore-fail-on-non-empty -p $tgt_dir
      fi
    done
    exit 0
}

sep_start()
{
  printf '###############################################################################\n'
}

sep_end()
{
  printf '###############################################################################\n\n'
}

subsep_start()
{
  printf '*******************************************************************************\n'
}

subsep_end()
{
  printf '*******************************************************************************\n'
}
################################################################################

is_in(){
    value=$1
    shift
    for var in $*; do
        [ $var = $value ] && return 0
    done
    return 1
}

die_unknown(){
    echo "Unknown option \"$1\"."
    echo "See $0 --help for available options."
    exit 1
}

set_default(){
    for opt; do
        eval : \${$opt:=\$${opt}_default}
    done
}

CMDLINE_SET='
    linux_src_dir
    kernel_modules_base_dir
    init_cpp
    init_cc
    init_ld
    init_ulinux_arch
    uevents_timeout
    gen_init_cpio
    kernel_release
    extra_modules
    pkg_config
    readelf
    elf_interpreter
'
#command line set defaults
#if the root is not around in less than 4s, something is really wrong
uevents_timeout_default=4000
gen_init_cpio_default='$linux_src_dir/usr/gen_init_cpio'
linux_src_dir_default=/usr/src/linux
#-------------------------------------------------------------------------------
#This defaults are for gcc, tested with version 4.7.3. You will need to
#override those for you compiler (tinycc/open64/pcc...). Additionnally, source
#support for different toolchains is not done.
#Since we use standard C runtime libs, be nice with the C runtime.
#The right way to do it is to have a toolchain abstraction layer since there are
#no standards for some
init_cpp_default='gcc -E -Wall -Wextra'
init_cc_default="gcc -Wall -Wextra -std=gnu99 -O0 \
-Wl,--dynamic-linker=/lib/ld.so -c"
init_ld_default='gcc -Wl,-O10,-s'
#-------------------------------------------------------------------------------
kernel_modules_base_dir_default=/
kernel_release_default=$(uname -r)
init_ulinux_arch_default=$(uname -m | sed -e s/i.86/i386/ -e s/parisc64/parisc/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/ -e s/sh.*/sh/)
extra_modules_default=
pkg_config_default=pkg-config
readelf_default=readelf
elf_interpreter_default=

set_default $CMDLINE_SET

show_help(){
    cat <<EOF
Usage: make [options] [operations]

Operations: [default is to build the the initramfs xz compressed cpio archive]:
  clean                                clean build products

Options: [defaults in brackets after descriptions]

Help options:
  --help                               print this message

Standard options:
  --lib-path-list=LIB_PATH_LIST        colon separated paths to look for target libraries
  --uevents-timeout=UEVENTS_TIMEOUT    uevents timeout in ms looking up for root block device to go online [$uevents_timeout]
  --quiet                              init will be silenced (output code compiled out)
  --linux-src-dir=DIR                  where to find the target linux source tree [$linux_src_dir_default]
  --kernel-release=RELEASE             the linux releases version [$kernel_release]
  --kernel-modules-base-dir=DIR        the base dir for linux modules and support files [$kernel_modules_base_dir]
  --extra-modules=EXTRA_MODULES        coma separated list of extra module to probe

Advanced options:
  --gen-init-cpio=GEN_INIT_CPIO_PATH   use this linux host tool to build the linux cpio initramfs [$gen_init_cpio_default]
  --pkg-config=PKG_CONFIG              use PKG_CONFIG pkg-config command for target libraries [$pkg_config_default]
  --readelf=READELF                    use READELF readelf command for target readelf [$readelf_default]
  --elf-interpreter=ELF_INTERPRETER    copy target ELF_INTERPRETER in cpio archive [will use the interpreter from the generated init elf binary]
  --init-cpp=CPP                       use CPP compiler command line CPP for target init process [$init_cpp_default]
  --init-cc=CC                         use C compiler command line CC for target init process objects [$init_cc_default]
  --init-ld=LD                         use linker command line LD for target init process [$init_ld_default]
  --init-ulinux-arch=ARCH              use ulinux ARCH for target init process [$init_ulinux_arch]
EOF
  exit 0
}

################################################################################

for opt do
    optval="${opt#*=}"
    case "$opt" in
        clean) clean_do
        ;;
        --help|-h) show_help
        ;;
        --quiet) CPPFLAGS="$CPPFLAGS -DQUIET"
        ;;
        *)
            optname=${opt%%=*}
            optname=${optname#--}
            optname=$(echo "$optname" | sed 's/-/_/g')
            if is_in $optname $CMDLINE_SET; then
                eval $optname='$optval'
            else
                die_unknown $opt
            fi
        ;;
    esac
done

################################################################################

sep_start;echo 'looking for source path:'
if test -f make; then
    src_path=.
else
    src_path=$(cd $(dirname "$0"); pwd)
    echo "$src_path" | grep -q '[[:blank:]]' &&
        die "out of tree builds are impossible with whitespace in source path."
    test -e "$src_path/config.h" &&
        die "out of tree builds are impossible with config.h in source dir."
fi
echo "source path is $src_path";sep_end

################################################################################

sep_start;echo 'checking libkmod and libblkid pkgconfig support:'
if $pkg_config --exists libkmod blkid; then
    echo "found pkg-config files for libkmod and libblkid"
else
    echo "missing pkg-config file for libkmod or libblkid"
    exit 1
fi
sep_end

################################################################################

#define variable in source
CPPFLAGS="$CPPFLAGS -DUEVENTS_TIMEOUT=$uevents_timeout"
CPPFLAGS="$CPPFLAGS $($pkg_config --cflags-only-I libkmod blkid)"
CFLAGS="$CFLAGS $($pkg_config --cflags-only-other libkmod blkid)"
LDFLAGS="$LDFLAGS $($pkg_config --libs libkmod blkid)"

################################################################################

sep_start;echo 'configure ulinux src tree for target arch:'
rm -f $src_path/ulinux/arch
ln -s archs/$init_ulinux_arch $src_path/ulinux/arch
echo "init ulinux arch is $init_ulinux_arch"
sep_end

################################################################################

#generated some code/headers

sep_start
revision=$(echo "$kernel_release" | egrep -o '^[[:digit:]]+\.[[:digit:]]+')
. $src_path/$revision
printf "linux revision is $revision, $src_path/$revision was sourced:\n--------\n"
cat $src_path/$revision
echo '--------'
sep_end

sep_start;echo 'generate static module list:'
$src_path/script/static_modules_h.sh $extra_modules $DISK_MODULES \
                                                             >./static_modules.h
cat ./static_modules.h
sep_end

################################################################################

sep_start;echo 'C preprocess init src files:'
for init_src_file in $init_src_files
do
    init_pp_c_file=${init_src_file%.c}
    init_pp_c_file=${init_pp_c_file}.pp.c
    echo "INIT_CPP $init_src_file->$init_pp_c_file"
    mkdir -p $(dirname $init_pp_c_file)
    $init_cpp $CPPFLAGS -I. -I$src_path -o $init_pp_c_file \
                                                        $src_path/$init_src_file
    init_pp_c_files="$init_pp_c_file $init_pp_c_files"
done
sep_end

################################################################################

sep_start;echo 'compile init preprocessed src files:'
for init_pp_c_file in $init_pp_c_files
do
    init_obj_file=${init_pp_c_file%.pp.c}
    init_obj_file=${init_obj_file}.o
    echo "INIT_CC $init_pp_c_file-->$init_obj_file"
    $init_cc $CFLAGS -o $init_obj_file $init_pp_c_file
    init_obj_files="$init_obj_file $init_obj_files"
done
sep_end

################################################################################

sep_start;echo 'link the init objects to produce the init binary:'
echo "INIT_LD $init_file_name"
$init_ld -o $init_file_name $init_obj_files $LDFLAGS
sep_end

################################################################################

#build the library path used by the gcc toolchain. We will need it to lookup
#for the dynamic shared libraries and copy them into the cpio
sep_start;echo "computing gcc library path:"
#in gcc search paths, the sysroot uses '='
lib_path_nosysroot=$($init_ld $LDFLAGS -print-search-dirs | egrep '^libraries' \
                                    | sed -r 's/libraries:[[:space:]]+(.+)/\1/')
echo "gcc library path is $lib_path_nosysroot"
gcc_sysroot=$($init_ld -print-sysroot)
echo "gcc_sysroot (can be empty) is $gcc_sysroot"

IFS=:
#replace the sysroot marker, '=', in gcc library path
if test -n "$gcc_sysroot"; then
  for p in $lib_path_nosysroot; do
    lib_path_noncanonical="$lib_path_noncanonical:$(echo "$p" | sed -r "s:^=:$gcc_sysroot:")"
  done
else
  for p in $lib_path_nosysroot; do
    lib_path_noncanonical="$lib_path_noncanonical:$(echo "$p" | sed -r 's/^=//')"
  done
fi

lib_path_noncanonical=${lib_path_noncanonical#:}

for p in $lib_path_noncanonical; do
  if readlink -m -q "$p" >/dev/null 2>&1; then
    lib_path="$lib_path:$(readlink -m "$p")"
  fi
done

lib_path=${lib_path#:}
echo "final LIB_PATH=$lib_path"
sep_end

################################################################################

#locate the target elf interpreter which will be copied in the cpio archive
sep_start
if test -z "$elf_interpreter"; then
  elf_interpreter=$($readelf -l ./init | egrep 'Requesting program interpreter' | sed -r 's/^.+interpreter:[[:space:]]*(.+)\]/\1/')
fi
echo "will copy $elf_interpreter elf interpreter in cpio archive"
sep_end

################################################################################

sep_start;echo 'generate the cpio source file:'
sed -e "s:@INIT_PATH@:$(readlink -f  $init_file_name):" "$src_path/cpio.in" >cpio
echo "dir /lib/modules/$kernel_release 0755 0 0">>cpio

modules=$HW_MODULES,$DISK_MODULES,$FS_MODULES
if test -n "$extra_modules";then
    modules=$modules,$extra_modules
fi

subsep_start
#add proper entries in the cpio definition file related to linux modules
$src_path/script/cpio_modules_add.sh $kernel_modules_base_dir $kernel_release \
                                                                 $modules ./cpio
subsep_end

subsep_start
#add proper entries in the cpio definition file related to shared elf libs
$src_path/script/cpio_libs_add.sh "$readelf" "$lib_path" ./init \
                                                       "$elf_interpreter" ./cpio
subsep_end

printf "cpio source file is:\n--------\n"
cat cpio
echo '--------'
sep_end

################################################################################

sep_start;echo 'generating and compressing the cpio archive'
e_gen_init_cpio=$(eval echo "$gen_init_cpio")
$e_gen_init_cpio cpio >${kernel_release}.cpio
xz --force --check=crc32 --extreme --stdout ${kernel_release}.cpio >${kernel_release}.cpio.xz
sep_end
